/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import { txtToMatrix, mdRender} from "../../../common/Common";

import { showMathStringInTable } from "../../../visual/Table2D";
import { drawDecisionTree } from "../../../visual/Tree2D";
import { DeNode,gen_tree,update_tree } from "../../../common/DecisionTreeFuncs";
import MatView2D, {createRandomPointStyleDict} from "../../../visual/MatView2D";
import Mat from "../../../common/Mat";

import 'katex/dist/katex.min.css';
import TDom from "../../../common/TDom";
import TCommon from "../../../common/TCommon";


class GBDT extends AlgorithmBase{
  constructor(){
    super('GBDT');

    this.model = {C:0.2}

    this.descriptionUrl = `${this.homepage}/data/slm/ch8/8.4/README.md`;
    this.datasetUrl     = `${this.homepage}/data/slm/ch8/8.4/dataset.txt`;
    this.x = [];
    this.y = [];
  }

  init(){ 
    this.reset();

    this.initTrainDS();
  }

  reset(){
    this.dataset = undefined;
    this.trainDS = undefined;

    this.clear();

    this.initDescription();
    this.initTrainDS();
    this.updateModel();
  }

  // 模型UI
  initModelUI(){
    this.addLabel(this.domElementList.model, '损失误差:');
    this.etaInput = this.addFloatInput(
      this.domElementList.model, this.model.C, 
      (event)=>{
        const val = parseFloat(event.target.value);
        if(val<0){
          this.model.C = 0
          event.target.value = 0
        }
        else this.model.C = val;
        this.updateModel();
      });
    this.modelDiv = this.addDiv(this.domElementList.model);
  }

  // 渲染模型到页面
  updateModel(x, y){

    if(x === undefined) x = [0];
    if(y === undefined) y = [1];
 
    var input_log = []
    input_log.push([`序号:`])
    for(var i=0;i<x.length;i++){
      input_log[0].push(`${i}`)
    }

    input_log.push([`年龄(x):`])
    for(var i=0;i<x.length;i++){
      input_log[1].push(`${x[i]}`)
    }

    input_log.push([`身高(y):`])
    for(var i=0;i<x.length;i++){
      input_log[2].push(`${y[i]}`)
    }

    if(this.inputDiv) this.inputDiv.innerHTML = '';
    showMathStringInTable(this.inputDiv, input_log);

    const modelString = String.raw
    `$$ \begin{aligned} C&:=${this.model.C} \end{aligned} $$`

    mdRender(`${modelString}`, this.outputDiv);
    
  }

  // 输入数据UI
  initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    // this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    TDom.createButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "训练", this.train.bind(this));

    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);

    this.fileSelector  = fileSelector;
  }

  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
  }

  // 获取描述md文件
  initDescription(){
    TDom.getDataFromUrl(this.descriptionUrl, (txt)=>{
      mdRender(txt, this.descriptionDiv);
    })

  }

  // 获取服务器数据集
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }

  // 准备数据训练数据
  prepare(url){
    TDom.getDataFromUrl(url, 
      (txt)=>{
      this.dataset = txtToMatrix(txt);
      this.trainDS = TCommon.loadDataset(this.dataset);
      let X = new Mat.Mat(this.trainDS.Mat);
      let y = new Mat.Mat(this.trainDS.Labels);
      for(var i=0;i<y.length;i++){
        y[i]=y[i][0]
      }
      var x=[]
      for(var i=0;i<X.length;i++){
        x.push(X[i][0])
      }
      this.pointStyleDict = createRandomPointStyleDict(this.trainDS.Labels);
      this.updateModel(x,y);
    })
  }

  openFileSelector(){
    this.fileSelector.click();
  }
  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }

  // 训练
  train(){
    const trainDS = this.trainDS;
    if(!trainDS) return;

    let X = new Mat.Mat(trainDS.Mat);
    let Y = new Mat.Mat(trainDS.Labels);

    for(var i=0;i<Y.length;i++){
      Y[i]=Y[i][0]
    }

    var x=[]
    for(var i=0;i<X.length;i++){
      x.push(X[i][0])
    }

    var y =[]
    for(var i=0;i<Y.length;i++){
      y.push(Y[i])
    }

    this.trainLog = []
    var count = 0
    this.trainLog.push(["迭代次数","残差","f_i(x)"])
    var c=0

    for(var i=0;i<y.length;i++) c+=y[i]
    c = c/y.length

    var tree = gen_tree(x,y,2,c)

    if(this.outputDiv) this.outputDiv.innerHTML = '';
    drawDecisionTree(this.outputDiv,tree);

    for(var i=0;i<5;i++){
      count++
      var rev = update_tree(x,y,tree)
      tree = Object.assign({},rev.a)
      var rx = rev.b
      var fx = rev.c
      var rx_str = `r_${count}=\\begin{cases}`,fx_str = `f(x)_${count}=\\begin{cases}`
      for(var j=0;j<rx.length;j++){
        rx_str+=`${rx[j]},\\\\`
        fx_str+=`${fx[j]},\\\\`
      }
      rx_str+=`\\end{cases}`
      fx_str+=`\\end{cases}`
      this.trainLog.push([`${count}`,rx_str,fx_str])
    }

    const modelString = String.raw
    `$$ \begin{aligned} 初始化f_0(x)&:=${JSON.stringify(c)} \end{aligned} $$`
    mdRender(`${modelString}`, this.outputDiv);
    showMathStringInTable(this.outputDiv, this.trainLog);
    drawDecisionTree(this.outputDiv,tree);

    this.labelTrainTips.innerText = '训练完毕';
  }
}


export default GBDT;